package com.scy.lock;

import cn.hutool.core.util.IdUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;

import java.util.Arrays;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

/**
 * @Author：sichenyong
 * @Email: sichenyongwork@163.com
 * @Package：com.scy.lock
 * @Project：Redis7NewDataStructure
 * @Date：2024/5/8 19:16
 * @description：
 */
@Slf4j
public class RedisDistributedLock implements Lock {

    private StringRedisTemplate stringRedisTemplate;
    private String lockName; // KEYS[1]
    private String uuidValue; // ARGV[1]
    private Long expireTime; // ARGV[2]

    public RedisDistributedLock(StringRedisTemplate stringRedisTemplate, String lockName, String uuidValue) {
        this.stringRedisTemplate = stringRedisTemplate;
        this.lockName = lockName;
        this.uuidValue = uuidValue;
        this.expireTime = 30L; // default
    }

    public RedisDistributedLock(StringRedisTemplate stringRedisTemplate, String lockName, Long expireTime) {
        this.stringRedisTemplate = stringRedisTemplate;
        this.lockName = lockName;
        this.expireTime = expireTime;
        this.uuidValue = IdUtil.simpleUUID() + ":" + Thread.currentThread().getId();
    }

    @Override
    public void lock() {
        tryLock();
    }



    @Override
    public boolean tryLock() {
        try {
            return tryLock(-1L, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            return false;
        }
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        if (time == -1L) {
            String script = "if redis.call('EXISTS', KEYS[1]) == 0 or redis.call('hexists', KEYS[1], ARGV[1]) == 1 then "
                                + "redis.call('HINCRBY', KEYS[1], ARGV[1], 1) "
                                + "redis.call('EXPIRE', KEYS[1], ARGV[2]) return 1 "
                          + "else " +
                                "return 0 " +
                            "end";
            // CAS自旋思想
            while (!stringRedisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class), Arrays.asList(lockName), uuidValue, String.valueOf(expireTime))) {
                // 等待50MS
                TimeUnit.MILLISECONDS.sleep(100L);
            }

            // 拿到锁
            return true;
        }

        return false;
    }

    @Override
    public void unlock() {
        String script = "if redis.call('hexists', KEYS[1], ARGV[1]) == 0 then return nil elseif redis.call('hincrby', KEYS[1], ARGV[1], -1) == 0 then return redis.call('del', KEYS[1]) else return 0 end";
        Long execute = stringRedisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Arrays.asList(lockName), uuidValue, String.valueOf(expireTime));
        if (execute == null) throw new RuntimeException(String.format("lock: 【%s】 doesn't exists", lockName));
    }

    @Override
    public Condition newCondition() {
        return null;
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {

    }

}
